<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> 
    <title> - 天地维杰网</title>
    <meta name="keywords" content="系统架构,shutdown,不与天斗,Domino,博客,程序员,架构师,笔记,技术,分享,java,Redis">
    
    <meta property="og:title" content="">
    <meta property="og:site_name" content="天地维杰网">
    <meta property="og:image" content="/img/author.jpg"> 
    <meta name="title" content=" - 天地维杰网" />
    <meta name="description" content="天地维杰网 | 博客 | 软件 | 架构 | Java "> 
    <link rel="shortcut icon" href="http://www.shutdown.cn/img/favicon.ico" />
    <link rel="apple-touch-icon" href="http://www.shutdown.cn/img/apple-touch-icon.png" />
    <link rel="apple-touch-icon-precomposed" href="http://www.shutdown.cn/img/apple-touch-icon.png" />
    <link href="http://www.shutdown.cn/js/vendor/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />
    <link href="http://www.shutdown.cn/js/vendor/fancybox/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
    <link href="http://www.shutdown.cn/css/main.css" rel="stylesheet" type="text/css" />
    <link href="http://www.shutdown.cn/css/syntax.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" id="hexo.configuration">
  var NexT = window.NexT || {};
  var CONFIG = {
    scheme: 'Pisces',
    sidebar: {"position":"left","display":"post"},
     fancybox: true, 
    motion: true
  };
</script>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7826003325059020" crossorigin="anonymous"></script>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<div class="container one-collumn sidebar-position-left page-home  ">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"> <div class="site-meta  custom-logo ">

  <div class="custom-logo-site-title">
    <a href="http://www.shutdown.cn"  class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <span class="site-title">天地维杰网</span>
      <span class="logo-line-after"><i></i></span>
    </a>
  </div>
  <p class="site-subtitle">人如秋鸿来有信，事若春梦了无痕</p>
</div>

<div class="site-nav-toggle">
  <button>
    <span class="btn-bar"></span>
    <span class="btn-bar"></span>
    <span class="btn-bar"></span>
  </button>
</div>

<nav class="site-nav">
    <ul id="menu" class="menu">
      
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />首页
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/redis/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-battery-full"></i> <br />Redis
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/java/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-coffee"></i> <br />java
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/linux/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-linux"></i> <br />linux
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/daily/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-bug"></i> <br />日常问题
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/spring/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-child"></i> <br />Spring和Springboot
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/mac/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-fire"></i> <br />Mac相关
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/middleware/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-gavel"></i> <br />中间件
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/jiagou/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-rocket"></i> <br />架构
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/python/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-ship"></i> <br />python
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/front/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-bolt"></i> <br />前端
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/jvm/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-balance-scale"></i> <br />jvm
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/c/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-battery-empty"></i> <br />c语言
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/categories/web3/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-web3"></i> <br />web3
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/post/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />归档
          </a>
        </li>
      
        <li class="menu-item ">
          <a href="http://www.shutdown.cn/about/" rel="section">
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />关于
          </a>
        </li>
      
      <li class="menu-item menu-item-search">
        <a href="javascript:;" class="popup-trigger"> <i class="menu-item-icon fa fa-search fa-fw"></i> <br /> 搜索</a>
      </li>
    </ul>
    <div class="site-search">
      <div class="popup">
 <span class="search-icon fa fa-search"></span>
 <input type="text" id="local-search-input">
 <div id="local-search-result"></div>
 <span class="popup-btn-close">close</span>
</div>

    </div>
</nav>

 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
<section id="posts" class="posts-expand">
  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <header class="post-header">
      <h1 class="post-title" itemprop="name headline">
        <a class="post-title-link" href="http://www.shutdown.cn/post/redis6.0%E6%96%B0%E7%89%B9%E6%80%A7-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BC%93%E5%AD%98%E5%85%B6%E4%B8%89/" itemprop="url">
        
        </a>
      </h1>
      <div class="post-meta">
      <span class="post-time">
<span class="post-meta-item-icon">
    <i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">时间：</span>
<time itemprop="dateCreated" datetime="2016-03-22T13:04:35+08:00" content="0001-01-01">
    0001-01-01
</time>
</span> 
      
      
       <span>
&nbsp; | &nbsp;
<span class="post-meta-item-icon">
    <i class="fa fa-eye"></i>
</span>
<span class="post-meta-item-text">阅读：</span>
<span class="leancloud-visitors-count">692 字 ~4分钟</span>
</span>
      </div>
    </header>
    <div class="post-body" itemprop="articleBody">
    

    

<blockquote>
<p>原文地址：<a href="https://www.kawabangga.com/posts/3590">https://www.kawabangga.com/posts/3590</a></p>
</blockquote>

<h1 id="redis客户端缓存设计-in-process-caching-https-www-kawabangga-com-posts-3590"><a href="https://www.kawabangga.com/posts/3590">Redis客户端缓存设计（In-Process caching）</a></h1>

<p>今天看 antirez 写了一篇有关客户端缓存设计的想法：《<a href="http://antirez.com/news/130">Client side caching in Redis 6</a>》（文章比较难懂，如果先看 Ben 演讲，理解起来 antirez 这篇博客会轻松一些）。antirez 认为，redis 接下来的一个重点是配合客户端，因为客户端缓存显而易见的可以减轻 redis 的压力，速度也快很多。大公司或多或少都有实现这种应用端缓存的机制，antirez 想通过 server 端的一些设计来减少客户端缓存实现的复杂度和成本，甚至不惜在 redis 协议上做修改。</p>

<p>antirez 的博客对细节介绍的比较清楚，我这篇文章算是拾人牙慧了，一来理清一下自己的思路，二来我认为除了缓存，这个机制还可以用在实时控制应用程序的某些配置上面。跟大家分享一下。</p>

<h2 id="一-缓存的数据一致性问题">一、缓存的数据一致性问题</h2>

<p>通常我们谈论缓存一致性的时候，一般在谈的都是这种架构：应用有一个数据库和缓存，数据库中的常用数据会被放到缓存中，在这种场景下如何保证数据库的数据和缓存中的数据是一致的？这种其实比较好解决，<a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside">Cache-aside 模式</a>已经是比较成熟和通用的了，实现上也比较简单和可靠。</p>

<blockquote>
<p><strong>Remember kids: you either have a single source of truth, or multiple sources of lies.</strong></p>
</blockquote>

<p>但 Redis 这种缓存从某种意义上还是一种远程的缓存，每次缓存读取会增加一次 TCP RTT（这个影响个人认为随着技术发展会逐渐减少），数据的序列化和反序列化也需要资源。如果对效率有更高的要求，就要考虑进程内缓存了。</p>

<p>进程内缓存的数据一致性比分布式的缓存面临更大的挑战。一个进程更新的时候，如何通知其他进程也更新自己的缓存呢？如果按照分布式缓存的思路，我们可以缩短缓存过期时间，进程内缓存如果过期了就去分布式缓存获取数据。这样不必实现复杂的通知机制，但是不同进程内的数据依然会面临不一致的问题，并且不同进程缓存过期时间不统一，用户体验也不好，同一个请求到了不同的进程，可能出现反复幻读的情况。另外也会对分布式进行大量不必要的更新，浪费网络资源。</p>

<p>进程内缓存面临2个主要的问题是：</p>

<ol>
<li>保证数据的一致性，包括各个进程缓存的数据要是一致的，进程缓存和 Redis 缓存要是一致的；</li>
<li>尽可能减小网络压力；</li>
</ol>

<p>为了实现所有进程的缓存的一致性，显而易见的实现是，当一个 key value 被修改了，广播被修改的键值对。所有客户端收到广播的时候更新自己的 kv。这种实现的缺点是1）有可能一个进程收到两个冲突的广播，无法解决。2）广播键值对，这样修改缓存的代价太大了。基于这两点，可以想到优化方案是我们只广播更新的 key，Redis 的缓存最为 Source of Truth，客户端收到了 key 更新的消息，就去 Redis 获得最新的键值对。这样就解决了冲突的问题，对资源的消耗也少了。</p>

<p>这里还可能存在的问题是：业务为了避免 key 冲突，通常会把 key 的名字写的很长：<code>project:app:username:lastlogin</code> 。这样 debug 起来也很简单。Redis 的 Key 长度限制是 512M，“如果一个问题有可能发生，那么它就一定会发生”。另一个问题是即使其他进程缓存了 key 但是用不到，也要立即去 Redis 获取最新的 value，也浪费了资源，当然这里可以用类似 Cache-aside 的策略，收到 key 即删除，下次用到的时候再取，这个问题不大。</p>

<p>所以 <a href="https://www.youtube.com/watch?v=kliQLwSikO4">Ben 视频</a>中提出了一个很有创意的实现。这里是借鉴了 Redis Cluster，所以我们先跑个题：Redis Cluster 是将 key Hash 到不同的 slots，每个 Node 都保存一个范围的 Slots（Shared nothing），当读请求过来的时候，对这个请求的 key Hash 计算 Slot，然后根据 Slot 去对应的 Node 寻找这个 Key。</p>

<p>基于这个 idea，为了节省空间（同时节省广播 Key 的网络资源和处理 Key 更新的资源），每一个 key 都属于一个 slot，在 key 更新的时候，我们只广播更新了这个 key 所在的 slot：“嘿，这个 slot 更新了，你不用管 slot 里面哪一个 key 更新了，只要你的 key 在这个 slot 里面，那就去读 Redis 里面的数据吧，进程内缓存的数据已经脏了！” Slot 是 2bytes，比 key 小多了，可以节省很多网络资源。</p>

<p>空间是节省了，随之而来的问题是更新逻辑变得复杂。如果广播 key，那客户端收到 key 即删除，下次从 Redis 取就好了。收到的是一个 slot 呢？Slot 是单向的（因为 hash 是单向的），有 key 可以计算出 slot，有了 slot 可不知道这个 slot 能包含哪些 key。</p>

<p>计算遍历所有的 key 吗？命中脏 slots 的话，就删除这个key？但是这样的话相当于对每一个缓存更新操作，客户端都要遍历计算一遍自己所有 key 的 slot，显然是不可接受的。</p>

<p>这里也是采用惰性计算的思想：客户端收到了 slot 更新的广播，只把 slot 存起来，当真正用到在此 slot 中的 key 的时候才去 Redis 更新。那么就会有这样一种情况，slot 中部分 key 更新了，部分 key 没有更新，如何区分开哪些 key 已经在 slot 更新之后更新过了呢？这里只要记一下 slot 更新的 timestamp 就可以，每一个 key-value 也带有一个 timestamp 属性。如果 key 的 timestamp 早于 slot 的 timestamp，那 key 就是需要更新的；更新之后 key 的 timestamp 就晚于 slot 的 timestamp 了。下次可以直接用。</p>

<p>客户端内缓存需要保存的内容，增加了 slot 和租后修改时间，kv 也增加了 timestamp 保存从 Redis 获得缓存的时间，用来判断这个 key 是否脏了。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220629111134.png" width = 900 /> </div>

<p>读缓存的流程：先将 kv 的 timestamp 和 slot 的 timestamp 对比，看 key 是否已脏。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220629111200.png" width = 900 /> </div>

<p>写操作比较直观，先写到 Redis，再写到 in-process cache，两个都洗完之后返回客户端缓存更新成功。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220629111223.png" width = 900 /> </div>

<p>这里还有一些 tricky 的情况：我更新了一个 key，刷新了这个 key 的 timestamp，同时又收到了这个 key 所在的 slot 更新的消息。这时客户端如何处理这种情况呢？以自己更新的为准还是收到的 slot 信息为准？这个问题可以用 <a href="https://en.wikipedia.org/wiki/Master_clock">Master clock</a> 时钟同步或 <a href="https://redis.io/topics/distlock">RedLock</a> 来解决，但是从并发性和系统复杂度方面讲，两个都不是最好的办法。</p>

<p>这里有一种无锁的实现，通过操作的顺序来保证数据一致，非常巧妙，实现的几个要点如下：</p>

<ol>
<li>所有的 timestamp 都是 server local timestamp：获得了 slot 更新的消息，将 slot 的更新时间设定为当前 local timestamp；进程更新 key 了，进程将 key 的更新时间设定为 local timestamp。实际上，timestamp 已经当成一个相对于本地 server 的偏移量来用了，无论是不同进程之间的时间如何偏移不准，都没有影响；</li>
<li>必须先更新 Redis，Redis 更新完成之后再发布更新消息（再次强调，<strong>Redis 作为 Source of the Truth</strong>）；</li>
<li>在更新 Redis、in-process cache 之前就获取 timestamp，这一步很关键。这里解释一下为什么要先获取 current timestamp 再进行更新：其根本目的是 slot、key 的 timestamp 就尽量提前。如果在 Get current timestamp 之前收到了 slot update message，那么我们的更新操作一定发生在其他进程的更新操作之后，没有毛病；如果在 Get current timestamp 之后收到了 slot update message，那么不管如何，我们的 key timestamp 会落后收到的 slot timestamp，会去 redis 获取，也没有毛病。假设这里先更新完再获取 timestamp，会有这么一种情况：我们更新好了 in-process cache，这时候来了一条 slot update message，我们更新了这个 slot 的 timestamp，然后我们自己的更新操作到了获取 timestamp 这一步，我们记录了自己的 key timestamp 和 slot timestamp。就造成了我们的 key 更新时间实际上晚于真正的 key 更新时间，我们保存了一个过时的 key 却不知道。</li>
</ol>

<p>以上说的有点啰嗦，总结一下：我们总是更新完 redis 缓存在发送同步消息，这样其他进程收到消息的时候，Redis 总是保存了最新的缓存；通过提前获取时间来保证了本地更新缓存和收到缓存更新时的冲突（倾向于认为自己的数据过时），并且没有锁、没有时间同步要求、不会保存了一个过时的 key 却认为是最新的。</p>

<p>最后其实还有一种非常极端的情况，但是有出现的可能，我觉得作者这个解决方法很牛，所以也说一下。</p>

<p>这个情况就是在一个 timestamp 分辨率下，我更新缓存，但同时收到了 slot 更新的消息。即我的 key 的 timestamp 是 a，但是在 a 这个 timestamp 的同时其他进程更新了缓存，这个时候 timestamp a 依然是正确的，但其实缓存住的是一个过时的 key。其实这个发生的概率太小了，timestamp 的精度是 300ns 的话，必须在 300ns 内更新完 redis 缓存和 in-process 缓存，收到 sync 消息，才有可能发生——但是依然有概率发生的。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220629111244.png" width = 900 /> </div>

<p>解决的方法很聪明：GetTimestamp() 之后总是 -1（其实不必-1，减一个 timestamp 精度就可以）.这样就保证收到 sync message 我总是倾向于缓存脏了，去 redis 获取。如果发生一个 timestamp 进度内出现缓存更新和收到 sync 消息，那么我实际缓存更新的时间肯定晚于其他进程更新缓存的时间，因为我把我的表调快了嘛，所以我保存的 key 是新的。</p>

<p>最后的方案还有一些小问题：如果 pub/sub 出现了网络问题，那么情况就退化成等待缓存过期的情况了。Pub/Sub 本身在 redis 中就不是可靠的，可以通过 Stream 或其他可靠的机制来替代。</p>

<p>Well well，以上就是在现有的 Redis 实现客户端缓存的一些设计，这个设计源自 <a href="https://www.youtube.com/watch?v=kliQLwSikO4">Redis Conf 的演讲</a>。Antirez 认为这个方法非常好，所以想在 Redis Server 端做一些支持。</p>

<h2 id="二-redis-6-客户端缓存加强">二、Redis 6 客户端缓存加强</h2>

<p>注意上面讲的都是 Client 端所做的实现，对 Redis Server 并没有任何改动，这意味着，使用当前的 Redis 完全是可以实现的。这个实现存在的问题有：</p>

<ol>
<li>pub/sub 不可靠，如果 sub 掉线，pub 端和 Server 并不做任何承诺，这个上面说过的；</li>
<li>广播的是 slot 信息，slot 中如果有一个 key 更新了，那么整个 slot 都认为脏了。一共有 16384 个 slot，这意味着如果我们有百万级别的 key 数量的话，将平均几百个 key 共享 slot，这样的话变脏的几率是很高的。这是一种 Trade off 吧，一个 slot 占用 16bit，16384 个 slots 一共占用 64Kib 的数据。毕竟客户端的的目的是最大程度的利用缓存；</li>
<li>一个 slot 更新的消息，Server 要广播给所有的客户端，即使那些客户端并没有缓存这个 slot。这是因为服务端无法知道哪些客户端缓存了哪些 slots；</li>
</ol>

<p>以上的问题，只有在 Server 端参与的情况下才可以更好地解决。所以 Redis 6 会实现 “Server-assisted client side caching”。</p>

<p>关于问题2和问题3：Redis 的 Command Table 里面会标记出来 Read Only 的命令，当客户端发送了 Read Only 的命令，比如 MGET，Server 除了返回响应之外，还会记住这个客户端读取了这个数据（数据所在的 slots）。这样 Server 会保持追踪哪个客户端缓存了哪些 slots，在 slot 变脏之后，只通知缓存这个 slot 的客户端。</p>

<p>实现的方式是保持一个很大的指针数组，每一个指针指向一个 Linked List，代表缓存了一个 Slot 的客户端列表。</p>
<div class="highlight"><pre style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-text" data-lang="text">slot1 -&gt; client1 -&gt; client2 -&gt; client5
slot2 -&gt; client2
slot3 -&gt; null
slot4 -&gt; client9 -&gt; client1 -&gt; client2
...
slot16777216 -&gt; client23 -&gt; client24</code></pre></div>
<p>每一个 client 都有一个 unique ID，第四行就表示，client 9，1，2 读取了 slot4 中的数据。client假设 slot4 中的 key 更新了，那么 Server 将会把 Slot 更新的信息发送给这三个客户端。这样就解决了问题3，无意义的消息推送问题。</p>

<p>这个指针数据有多大呢？首先这里的 slots 数量比 Ben 的方法多很多，这里使用的是 CRC64 的 24bit 输出，那就是 2^24 个 slots。指针是 64bit 指针，即 64bit ** 2^^24 = ~130Megabyte。Antirez 认为服务端这些内容是是值的，这解决了很多 key 共享 slots 的问题，就算有千万级别的 key，也只有两三个 key 共享 slots，散列度很高。</p>

<p>有2点需要注意：客户端可以重新 match 这些 slots，服务端是 16million 个，客户端可以使用更少，收到 slots 信息自己做 mapping；这个 feature 可以选择开关，通过 <code>CLIENT TRACKING on</code> 来打开。</p>

<p>关于问题1：Antirez 建议客户端感知连接的断开，处理好重连的情况；建议有一个 Thead 来 ping connection 确保连接存活。Redis Server 为了尽量减少影响，以及方便客户端的实现，还会通知其他客户端：请转发此消息（slot invalidation）给掉线客户端。</p>

<p>另外这样的实现在 Redis 协议里面有些冲突，客户端调用 block 命令收到的回复和收到的 slot push message 回复如何区分？Antirez 直接没有提复杂的实现（比如从 Response 中添加额外的信息标志这个是正常回复，还是 Invalidation message?），而是建议另开一个 Connection，专门处理 Invalidation messages，不和接收正常回复的 Connection 共享一个 Connection Pool。客户端可以通过发送命令 <code>CLIENT TRACKING on REDIRECT 1234</code> 来告诉服务器有一个专门的客户端是接收 Invalidation messages 的。</p>

<p>以上已经合并到 unstable。其实我倒觉得，现在同 datacenter 内的网速已经很快了，增加这样的复杂度来换取客户端缓存的优势好像并不值得，实际我也没有接触过客户端缓存，也不知道会带来多少的性能提升。但是我看到这个实现我马上想到了一个非常实用的使用场景。</p>

<h2 id="三-客户端缓存的应用场景-动态配置数据">三、客户端缓存的应用场景：动态配置数据</h2>

<p>复杂系统中我们肯定有动态修改配置的需求：比如如果 condition-1 就这样处理，如果 condition-2 就那样处理。每一个请求都需要判断这个 condition，这个 condition 又经常变。</p>

<p>如果放到数据库里面，通过数据库来修改，显然对数据库的压力就太大了。一般我们会实现某种形式的通知系统。类似下面的这种结构。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220629111507.png" width = 900 /> </div>

<p>对支付宝中间件熟悉的同学，可以发现这就是 DRM（动态配置中心，Dynamic Resource Management）。现在有了 Redis 这种机制的话，我们可以很轻松的实现一个可靠的动态配置系统。</p>

<p>下面这张图很直白的表示了什么样的数据，适合什么样的缓存。</p>

<div  align="center"><img src="https://wejack639.oss-cn-beijing.aliyuncs.com/blogimages/img/20220629111516.png" width = 900 /> </div>

<p>Source: <a href="https://www.youtube.com/watch?v=kliQLwSikO4">https://www.youtube.com/watch?v=kliQLwSikO4</a></p>

<p>在这张图中，配置类型的数据几乎是属于最右下角的了，可能每一个请求都需要读取配置数据，访问频率非常高，但是改动很小。除此之外，像是对于每次请求都要通过 cookie 判断用户登陆状态的情况，对 session 的读取压力也是非常大的，这种场景其实做到客户端缓存中也比较合适。</p>

<p>综上，Client-caching 我认为意义是很大的。如果你有 Redis，那么你不需要引入其他依赖，不需要 Message Queue，不需要同步时钟，不需要锁，就可以得到一个近乎强一致的客户端缓存机制。</p>

<p>参考资料：</p>

<ol>
<li><a href="https://www.youtube.com/watch?v=kliQLwSikO4">RedisConf18: Techniques for Synchronizing In Memory Caches with Redis – Redis Labs</a> （<a href="https://www.slideshare.net/RedisLabs/redisconf18-techniques-for-synchronizing-inmemory-caches-with-redis">Slide</a>）</li>
<li><a href="https://github.com/bmalec">bmalec</a>/<a href="https://github.com/bmalec/RedisMultilevelCache">RedisMultilevelCache</a></li>
<li><a href="https://groups.google.com/forum/#!msg/redis-db/xfcnYkbutDw/kTwCozpBBwAJ">Client side caching: initial design ideas</a></li>
<li><a href="http://antirez.com/news/130">Client side caching in Redis 6</a></li>
<li><a href="https://blog.csdn.net/z50L2O08e2u4afToR9A/article/details/80809269">进程内缓存，究竟怎么玩？</a></li>
<li><a href="https://blog.csdn.net/wabiaozia/article/details/80027589">进程内缓存与分布式缓存的比较</a></li>
<li><a href="https://coolshell.cn/articles/17416.html">缓存更新的套路</a></li>
</ol>

    </div>
    <footer class="post-footer">
     

     <div class="post-nav">
    <div class="post-nav-next post-nav-item">
    
        <a href="http://www.shutdown.cn/post/redis6.0%E6%96%B0%E7%89%B9%E6%80%A7-tls/" rel="next" title="">
        <i class="fa fa-chevron-left"></i> 
        </a>
    
    </div>

    <div class="post-nav-prev post-nav-item">
    
        <a href="http://www.shutdown.cn/post/redis6.0%E6%96%B0%E7%89%B9%E6%80%A7-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BC%93%E5%AD%98%E5%85%B6%E4%BA%8C/" rel="prev" title="">
         <i class="fa fa-chevron-right"></i>
        </a>
    
    </div>
</div>
      
     
     
     






    </footer>
  </article>
</section>

          </div>
        </div>
        <div class="sidebar-toggle">
  <div class="sidebar-toggle-line-wrap">
    <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
    <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
    <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
  </div>
</div>
<aside id="sidebar" class="sidebar">
  <div class="sidebar-inner">

    <section class="site-overview sidebar-panel  sidebar-panel-active ">
      <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image"
        src="http://www.shutdown.cn/img/author.jpg"
        alt="不与天斗Domino" />
    <p class="site-author-name" itemprop="name">不与天斗Domino</p>
    <p class="site-description motion-element" itemprop="description"> 
        Programmer &amp; Architect</p>
</div>
      <nav class="site-state motion-element">
    <div class="site-state-item site-state-posts">
      <a href="http://www.shutdown.cn/post/">
        <span class="site-state-item-count">183</span>
        <span class="site-state-item-name">日志</span>
      </a>
    </div>
    <div class="site-state-item site-state-categories">    
        <a href="http://www.shutdown.cn/categories/">      
         
        <span class="site-state-item-count">15</span>
        
        <span class="site-state-item-name">分类</span>
        
        </a>
    </div>

    <div class="site-state-item site-state-tags">
        <a href="http://www.shutdown.cn/tags/">
         
        <span class="site-state-item-count">224</span>
        
        <span class="site-state-item-name">标签</span>
        </a>
    </div>
</nav>
      
      

      

      <div class="links-of-blogroll motion-element inline">
<script type="text/javascript" src="//rf.revolvermaps.com/0/0/8.js?i=&amp;m=0&amp;s=220&amp;c=ff0000&amp;cr1=ffffff&amp;f=arial&amp;l=33&amp;bv=35" async="async"></script>
</div>

    </section>
    
  </div>
</aside>

      </div>
    </main>
   
    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright" >
  <span itemprop="copyrightYear">  &copy; 
  2013 - 2023</span>
  <span class="with-love"><i class="fa fa-heart"></i></span>
  <span class="author" itemprop="copyrightHolder">天地维杰网</span>
  <span class="icp" itemprop="copyrightHolder"><a href="https://beian.miit.gov.cn/" target="_blank">京ICP备13019191号-1</a></span>
</div>
<div class="powered-by">
  Powered by - <a class="theme-link" href="http://gohugo.io" target="_blank" title="hugo" >Hugo v0.63.2</a>
</div>
<div class="theme-info">
  Theme by - <a class="theme-link" href="https://github.com/xtfly/hugo-theme-next" target="_blank"> NexT
  </a>
</div>


      </div>
    </footer>

    <div class="back-to-top">
      <i class="fa fa-arrow-up"></i>
      <span id="scrollpercent"><span>0</span>%</span>
    </div>
  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/jquery/index.js?v=2.1.3"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/fastclick/lib/fastclick.min.js?v=1.0.6"></script> 
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/velocity/velocity.min.js?v=1.2.1"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/vendor/velocity/velocity.ui.min.js?v=1.2.1"></script>
<script src="http://www.shutdown.cn/js/vendor/ua-parser-js/dist/ua-parser.min.js?v=0.7.9"></script>

<script src="http://www.shutdown.cn/js/vendor/fancybox/jquery.fancybox.pack.js?v=2.1.5"></script>

<script type="text/javascript" src="http://www.shutdown.cn/js/utils.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/motion.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/affix.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/schemes/pisces.js"></script>

<script type="text/javascript" src="http://www.shutdown.cn/js/scrollspy.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/post-details.js"></script>
<script type="text/javascript" src="http://www.shutdown.cn/js/toc.js"></script>

<script type="text/javascript" src="http://www.shutdown.cn/js/bootstrap.js"></script>

<script type="text/javascript" src="http://www.shutdown.cn/js/search.js"></script>
<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    extensions: ["tex2jax.js"],
    jax: ["input/TeX", "output/HTML-CSS"],
    tex2jax: {
      inlineMath: [ ['$','$'] ],
      displayMath: [ ['$$','$$'] ],
      processEscapes: true
    },
    "HTML-CSS": { fonts: ["TeX"] }
  });
</script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML' async></script>
</body>
</html>